#include <buola/gui.h>

#include <buola/algorithm/container.h>

#include <buola/gui/cpropertyfield.h>

#include <buola/widgets/ctreewidget.h>

namespace buola { namespace gui {

/////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////
/////////////////////////// SNode /////////////////////////
/////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////

CTreeWidget::SNode::SNode(CTreeWidget *pTree,SNode *pParent)
{
    mTree=pTree;
    mParent=pParent;
    mSelected=false;
    mExpanded=false;
    mExpandable=false;
    mOnceExpanded=false;
    mIcon=nullptr;
    mNextVisible=nullptr;
    mRendererData=nullptr;
    mNext=nullptr;
    mPrevious=nullptr;
    mDepth=0;

    for(int i=0;i<mTree->GetFieldCount();i++)
        AddField(mTree->mTWF[i]->mField);
}

CTreeWidget::SNode::~SNode()
{
    mTree->OnNodeRemoved(this);

    for(int i=0;i<mChildren.size();i++)
    {
        delete mChildren[i];
    }

    mChildren.clear();
}

void CTreeWidget::SNode::AddChild(SNode *pChild)
{
    if(mChildren.size())
    {
        mChildren[mChildren.size()-1]->mNext=pChild;
        pChild->mPrevious=mChildren[mChildren.size()-1];
    }
    else
    {
        pChild->mPrevious=nullptr;
    }
    mChildren.push_back(pChild);
    pChild->mNext=nullptr;
    pChild->mDepth=mDepth+1;
}

void CTreeWidget::SNode::RemoveChild(SNode *pChild)
{
    int lIndex=std::find(mChildren.begin(),mChildren.end(),pChild)-mChildren.begin();
    if(lIndex>=0)
    {
        if(pChild->mPrevious)
        {
            pChild->mPrevious->mNext=pChild->mNext;
        }
        if(pChild->mNext)
        {
            pChild->mNext->mPrevious=pChild->mPrevious;
        }
        mChildren.erase(mChildren.begin()+lIndex);
    }
}

void CTreeWidget::SNode::Expand()
{
    if(mExpanded) return;
    mExpanded=true;
    if(!mOnceExpanded)
    {
        mTree->OnNodeExpandedFirstTime(this);
        mOnceExpanded=true;
    }
    mTree->OnNodeExpanded(this);
}

void CTreeWidget::SNode::ExpandTo()
{
    if(mParent)
    {
        mParent->ExpandTo();
        mParent->Expand();
    }
}

void CTreeWidget::SNode::Collapse()
{
    mExpanded=false;
    mTree->OnNodeExpanded(this);
}

CTreeWidget::SNode *CTreeWidget::SNode::GetNext()
{
    if(mChildren.size()) return mChildren[0];
    if(mNext) return mNext;
    SNode *lAncestors=mParent;
    while(lAncestors)
    {
        if(lAncestors->mNext) return lAncestors->mNext;
        lAncestors=lAncestors->mParent;
    }
    return nullptr;
}

CTreeWidget::SNode *CTreeWidget::SNode::GetNextVisible()
{
    if(mChildren.size()&&mExpanded) return mChildren[0];
    if(mNext) return mNext;
    SNode *lAncestors=mParent;
    while(lAncestors)
    {
        if(lAncestors->mNext) return lAncestors->mNext;
        lAncestors=lAncestors->mParent;
    }
    return nullptr;
}

CTreeWidget::SNode *CTreeWidget::SNode::GetPrevious()
{
    if(mPrevious)
    {
        SNode *lNode=mPrevious;
        while(lNode->mChildren.size())
        {
            lNode=lNode->mChildren[lNode->mChildren.size()-1];
        }
        return lNode;
    }
    return mParent;
}

CTreeWidget::SNode *CTreeWidget::SNode::GetPreviousVisible()
{
    if(mPrevious)
    {
        SNode *lNode=mPrevious;
        while(lNode->mChildren.size()&&lNode->mExpanded)
        {
            lNode=lNode->mChildren[lNode->mChildren.size()-1];
        }
        return lNode;
    }
    return mParent;
}

CTreeWidget::SNode *CTreeWidget::SNode::FindNode(int pIndex)
{
    if(pIndex==mIndex) return this;

    for(int i=0;i<mChildren.size();i++)
    {
        SNode *lReturn=mChildren[i]->FindNode(pIndex);
        if(lReturn) return lReturn;
    }
    return nullptr;
}

CTreeWidget::SNode *CTreeWidget::SNode::FindNode(const std::wstring &pText)
{
    if(pText==mText) return this;

    for(int i=0;i<mChildren.size();i++)
    {
        SNode *lReturn=mChildren[i]->FindNode(pText);
        if(lReturn) return lReturn;
    }
    return nullptr;
}

void CTreeWidget::SNode::AddField(CPropertyField *pField)
{
    pField->mType->InitValue(construct_back(mValues));
}

void CTreeWidget::SNode::SetColumnData(std::size_t pSlot,void *pData)
{
    if(pSlot>=mColumnData.size())
    {
        mColumnData.resize(pSlot+1,nullptr);
    }
    mColumnData[pSlot]=pData;
}

void *CTreeWidget::SNode::GetColumnData(std::size_t pSlot)
{
    if(pSlot>=mColumnData.size()) return nullptr;
    return mColumnData[pSlot];
}

UAny &CTreeWidget::SNode::Value(int i)
{
    return *mValues[mTree->GetFieldNum(i)];
}

std::wstring CTreeWidget::SNode::ValueAsText(int)
{
    ///\todo
    return L""; //utf32(mValues[mTree->GetFieldNum(i)]->ToString());
}

void CTreeWidget::SNode::SetText(const std::wstring &pText)
{
    mText=pText;
    mTree->OnNodeChanged(this);
}

int CTreeWidget::SNode::SortAscending(const void *p1,const void *p2)
{
    SNode *l1=*((SNode* const*)p1);
    SNode *l2=*((SNode* const*)p2);

    return (l1->mText.compare(l2->mText));
}

int CTreeWidget::SNode::SortDescending(const void *p1,const void *p2)
{
    SNode *l1=*((SNode* const*)p1);
    SNode *l2=*((SNode* const*)p2);

    return (-l1->mText.compare(l2->mText));
}

/////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////
////////////////////////// CTreeWidget //////////////////////////
/////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////

int CTreeWidget::GetFieldNum(int pIndex)
{
    int lCount=mTWF.size();
    for(int i=0;i<lCount;i++)
        if(mTWF[i]->mIndex==pIndex)
            return i;
    return -1;
}

CTreeWidget::SNode *CTreeWidget::GetNode(int pIndex)
{
    SNode *lReturn=nullptr;
    for(int i=0;i<mTWN.size();i++)
    {
        lReturn=mTWN[i]->FindNode(pIndex);
        if(lReturn) return lReturn;
    }
    return lReturn;
}

void CTreeWidget::TWCopyTree(CTreeWidget *pOther)
{
    for(int i=0;i<pOther->mTWN.size();i++)
    {
        mTWN.push_back(pOther->mTWN[i]);
    }
    for(int i=0;i<pOther->mTWF.size();i++)
    {
        mTWF.push_back(pOther->mTWF[i]);
    }
    mTWNextIndex=pOther->mTWNextIndex;
}

int CTreeWidget::AddField(CPropertyField *pField,int pIndex)
{
    SField *lNew=new SField;

    if(pIndex<0) pIndex=mTWNextIndex++;

    lNew->mIndex=pIndex;
    lNew->mField=pField;

    SField *lLastVisible=nullptr;

    for(int i=mTWF.size()-1;i>=0;i--)
    {
        if(!(pField->mStyle&CPropertyField::PLF_HIDDEN))
        {
            lLastVisible=mTWF[i];
            break;
        }
    }

    if(lLastVisible) //not first
    {
        lNew->mX=lLastVisible->mX+lLastVisible->mW;
        lNew->mW=lLastVisible->mW;
    }
    else
    {
        lNew->mX=200;
        lNew->mW=100;
    }

    mTWF.push_back(lNew);

    for(int i=0;i<mTWN.size();i++)
        mTWN[i]->AddField(pField);

    OnFieldsChanged();

    return lNew->mIndex;
}

bool CTreeWidget::RemoveField(int pIndex)
{
    fatal_error("RemoveField not implemented\n");

    int lPos=GetFieldNum(pIndex);
    if(lPos<0) return false;
    delete mTWF[lPos];
    mTWF.erase(mTWF.begin()+lPos);

    OnFieldsChanged();

    return true;
}

void CTreeWidget::RemoveAllFields()
{
    fatal_error("RemoveallFields not implemented\n");

    for(int i=0;i<mTWF.size();i++)
    {
        delete mTWF[i];
    }

    mTWF.clear();

    OnFieldsChanged();
}

CTreeWidget::SNode *CTreeWidget::NewNode(SNode *pParent,const std::wstring &pName,PIcon pIcon,
                                                        int pIndex)
{
    SNode *lNew=new SNode(this,pParent);

    if(pIndex<0) pIndex=mTWNextIndex++;

    lNew->mIndex=pIndex;
    lNew->mText=pName;
    lNew->mIcon=pIcon;

    return lNew;
}

CTreeWidget::SNode *CTreeWidget::AddNode(SNode *pNew)
{
    if(pNew->mParent)
    {
        pNew->mParent->AddChild(pNew);
    }
    else
    {
        if(mTWN.size())
        {
            mTWN[mTWN.size()-1]->mNext=pNew;
            pNew->mPrevious=mTWN[mTWN.size()-1];
        }
        else
        {
            pNew->mPrevious=nullptr;
        }

        mTWN.push_back(pNew);
    }

    OnNodeAdded(pNew);

    return pNew;
}

CTreeWidget::SNode *CTreeWidget::AddNode(SNode *pParent,const std::wstring &pName,PIcon pIcon,
                                                                int pIndex)
{
    return AddNode(NewNode(pParent,pName,pIcon,pIndex));
}

void CTreeWidget::RemoveNode(SNode *pNode)
{
    if(pNode==mTWSelectedNode)
        mTWSelectedNode=nullptr;

    if(pNode->mParent)
        pNode->mParent->RemoveChild(pNode);
    else
    {
        if(pNode->mPrevious)
        {
            pNode->mPrevious->mNext=pNode->mNext;
        }
        if(pNode->mNext)
        {
            pNode->mNext->mPrevious=pNode->mPrevious;
        }
        mTWN.erase(std::find(mTWN.begin(),mTWN.end(),pNode));
    }

    delete pNode;
}

bool CTreeWidget::RemoveNodeByID(int pIndex)
{
    SNode *lNode=GetNode(pIndex);
    if(!lNode) return false;

    RemoveNode(lNode);

    return true;
}

void CTreeWidget::RemoveAllNodes()
{
    mTWSelectedNode=nullptr;

    std::vector<SNode*> lOldTBN=mTWN;
    mTWN.clear();

    for(int i=0;i<lOldTBN.size();i++)
    {
        delete lOldTBN[i];
    }

    OnTreeChanged();
}

int CTreeWidget::GetSel()
{
    return mTWSelectedNode?mTWSelectedNode->mIndex:-1;
}

std::wstring CTreeWidget::GetSelText()
{
    return mTWSelectedNode?mTWSelectedNode->mText:std::wstring();
}

CTreeWidget::SNode* CTreeWidget::GetSelNode()
{
    return mTWSelectedNode;
}

void CTreeWidget::SetNodeText(int pIndex,const std::wstring &pString)
{
    SNode *lNode=GetNode(pIndex);
    if(lNode)
        lNode->mText=pString;
}

std::wstring CTreeWidget::GetNodeText(int pIndex)
{
    SNode *lNode=GetNode(pIndex);
    if(lNode)
        return lNode->mText;
    return(std::wstring());
}

CTreeWidget::SNode *CTreeWidget::FindNode(const std::wstring &pText)
{
    int lCount=GetNodeCount();
    for(int i=0;i<lCount;i++)
    {
        SNode *lReturn;

        lReturn=mTWN[i]->FindNode(pText);
        if(lReturn) return lReturn;
    }
    return nullptr;
}

CTreeWidget::SField *CTreeWidget::GetField(int pIndex)
{
    int lPos=GetFieldNum(pIndex);
    if(lPos>=0) return mTWF[lPos];
    return nullptr;
}

void CTreeWidget::SelectAll()
{
    mTWSelected=0;
    mTWSelectedNode=nullptr;

    for(int i=0;i<GetNodeCount();i++)
    {
        SelectAllUnder(mTWN[i]);

        if(mTWN[i]->mSelected) continue;

        mTWN[i]->ExpandTo();
        mTWN[i]->mSelected=true;

        OnNodeSelected(mTWN[i]);

        mTWSelected++;
    }
}

void CTreeWidget::UnselectAll()
{
    for(int i=0;i<GetNodeCount();i++)
    {
        UnselectAllUnder(mTWN[i]);

        if(!mTWN[i]->mSelected) continue;

        mTWN[i]->mSelected=false;

        OnNodeSelected(mTWN[i]); //Not selected
    }

    mTWSelectedNode=nullptr;
    mTWSelected=0;
}

void CTreeWidget::SelectNode(SNode *pNode)
{
    if(!pNode)
        return;

    pNode->ExpandTo();

    if(pNode->mSelected&&mTWSelected==1) return;

    std::vector<SNode*> lSelection;
    lSelection.push_back(pNode);

    UnselectAll();
    SelectNodes(lSelection);

    mTWSelectedNode=pNode;
    mTWSelected=1;
}

void CTreeWidget::SelectNode(int pIndex)
{
    SelectNode(GetNode(pIndex));
}

void CTreeWidget::ToggleNode(SNode *pNode)
{
    pNode->mSelected=!pNode->mSelected;

    OnNodeSelected(pNode);

    mTWSelected=(pNode->mSelected)?(mTWSelected+1):(mTWSelected-1);
}

void CTreeWidget::ToggleNode(int pIndex)
{
    ToggleNode(GetNode(pIndex));
}

void CTreeWidget::SelectNodesUnder(const std::vector<SNode*> &pNodes,SNode *pUnder)
{
    for(int i=0;i<pUnder->mChildren.size();i++)
    {
        SNode *lNode=pUnder->mChildren[i];

        SelectNodesUnder(pNodes,lNode);

        if(std::find(pNodes.begin(),pNodes.end(),lNode)!=pNodes.end())
        {
            //found in list
            if(lNode->mSelected) continue;

            lNode->ExpandTo();
            lNode->mSelected=true;

            OnNodeSelected(lNode);
        }
        else
        {
            //not found
            if(!lNode->mSelected) continue;
            lNode->mSelected=false;

            OnNodeSelected(lNode);
        }
    }
}

void CTreeWidget::SelectNodes(const std::vector<SNode*> &pNodes)
{
    for(int i=0;i<GetNodeCount();i++)
    {
        SNode *lNode=mTWN[i];

        SelectNodesUnder(pNodes,lNode);

        if(std::find(pNodes.begin(),pNodes.end(),lNode)!=pNodes.end())
        {
            //found in list
            if(lNode->mSelected) continue;

            lNode->ExpandTo();
            lNode->mSelected=true;

            OnNodeSelected(lNode);
        }
        else
        {
            //not found
            if(!lNode->mSelected) continue;
            lNode->mSelected=false;

            OnNodeSelected(lNode);
        }

    }

    mTWSelected=pNodes.size();
    mTWSelectedNode=nullptr;
}

void CTreeWidget::SelectAllUnder(SNode *pUnder)
{
    for(int i=0;i<pUnder->mChildren.size();i++)
    {
        SelectAllUnder(pUnder->mChildren[i]);

        if(pUnder->mChildren[i]->mSelected) continue;

        pUnder->mChildren[i]->ExpandTo();
        pUnder->mChildren[i]->mSelected=true;

        OnNodeSelected(pUnder->mChildren[i]);

        mTWSelected++;
    }
}

void CTreeWidget::UnselectAllUnder(SNode *pUnder)
{
    for(int i=0;i<pUnder->mChildren.size();i++)
    {
        UnselectAllUnder(pUnder->mChildren[i]);

        if(!pUnder->mChildren[i]->mSelected) continue;

        pUnder->mChildren[i]->mSelected=false;

        OnNodeSelected(pUnder->mChildren[i]);
    }
}

std::size_t CTreeWidget::AcquireColumnDataSlot(void *pData)
{
    if(mColumnData.mCount<mColumnData.mSlots.size())
    {
        for(int i=0;i<mColumnData.mSlots.size();i++)
        {
            if(!mColumnData.mSlots[i])
            {
                mColumnData.mSlots[i]=pData;
                mColumnData.mCount++;
                return i;
            }
        }

        //we will never get here
        assert(false);
    }

    mColumnData.mSlots.push_back(pData);
    return mColumnData.mCount++;
}

void CTreeWidget::ReleaseColumnDataSlot(std::size_t pSlot)
{
    assert(pSlot<mColumnData.mSlots.size());
    assert(mColumnData.mSlots[pSlot]);
    mColumnData.mSlots[pSlot]=nullptr;
    mColumnData.mCount--;
}

/*namespace gui*/ } /*namespace buola*/ }
